package MariposaDB::Client;

use strict;

our @ISA = qw/Object::Event/; # gives us event support

use Object::Event;
use Log::Log4perl qw(:easy);
use Data::Dumper;
use Coro;
use Coro::Signal;

use String::Random;
use MariposaDB::Utils;

use MariposaDB::Data::Messages;


sub new
{   
    my $class = shift;
    my ($self)  = @_;

    $self = $self || {};
    bless ($self, $class);

    $self->{'host'} ||= "localhost";
    $self->{'port'} ||= 30009;

    # for the TLS server side, use accept, and for the TLS client side of a connection, use connect
    $self->{'tls'} ||= undef; # just a reminder of the option

    # we have two queues: a read queue, and a write queue
    $self->{'write_queue'} = [];
    $self->{'read_queue'} = [];

    # some signals
    $self->{'new_incoming_signal'} = new Coro::Signal;

    # setup reader/writer queues
    # when we get a new_outbound_message event, we'll push it out
    $self->reg_cb ('new_outbound_message' => sub { DEBUG "New out event."; $self->_send_next_message(); } );

    $self->{'client_handle'} = "";
    $self->{'watch'} = AnyEvent::Socket::tcp_connect 
	(
	 $self->{'host'}, 
	 $self->{'port'},
	 sub 
	 {
	    my ($sock) = @_;

	    unless ($sock) 
	    {
		warn "couldn't connect: $!";
		return;
	    }

	    $self->{'client_handle'} = 
		AnyEvent::Handle->new (
		    'fh' => $sock,
		    'tls' => $self->{'tls'},
		    'timeout' => 0, #disabled
		    'on_timeout' => sub { },
		    'on_eof' => sub 
		    {
			DEBUG "received eof\n";
			undef $self->{'client_handle'}; 
			$self->{'connected'} = 0;
		    },
		    'on_error' => sub
		    {
			WARN "on_error?";
		    }
		);
	    
	    DEBUG "HHHHERE";
	    $self->{'connected'} = 1;


	    DEBUG "Setting up reader next chunk";
	    # reader 
	    $self->_read_next_chunk(); # this reads completed messages into read_queue.

	    # throw an event to let others know we connected, so they can do whatever it is they want to do.
	    DEBUG "Throwing on_connect";
	    $self->event('on_connect', 
			 {
			     'handle' => $self->{'client_handle'},
			 });
			 
	 }
	    
	    , 
	    sub {
		$self->{'timeout'}
	    }
	 );

    
    return $self;
}

sub get_message
{
    my $self = shift;

    my ($queue) = @_;
    $queue = $queue || "read_queue";
    
    # returns the first message in our queue
    return shift @{ $self->{$queue} };
}

sub _read_command_chunk
{
    my ($self, $handle) = @_;
    
    DEBUG "rcc";

    return unless defined $handle->{'rbuf'};

    DEBUG "Got buf: ".$handle->{'rbuf'};

    foreach my $line (split /\r*\n/, $handle->{'rbuf'})
    {
	# if we're in the middle of reading a command, keep at it
	if ($self->{'client_state'} eq "msg")
	{
	    if (substr($line,0,4) ne "msg ")
	    {
		# this is a continuation of a previous read
		$self->{'message'} .= $line."\n";
	    }
	    elsif ($line eq ("msg ".$self->{'delim'}))
	    {
		# end of message
		chop $self->{'message'}; # remove last \n

		# add to our internal buffer for later reads
		push (@{ $self->{'read_queue'} }, $self->{'message'});

		DEBUG "End of message, sending event.";
		DEBUG "msg:'".$self->{'message'}."'";
		$self->{'new_incoming_signal'}->send();
		$self->event('new_incoming_message'); #access with $self->get_message


		# reset
		$self->{'delim'} = undef;
		$self->{'message'} = "";
		$self->{'client_state'} = undef;
	    }
	    else
	    {
		# the line just happens to start with "msg "
		# this is a continuation of a previous read                                                                                                                                   
                DEBUG "line just happens to have msg a start";
		$self->{'message'} .= $line."\n";
	    }
	}
	else
	{
	    # no state, see what the command is
	    if ($line =~ /^msg (.+)\s*(.*)/)
	    {
		my $delim = $1;
		chomp $delim;
		$self->{'delim'} = $delim; # the current msg delim
		DEBUG "Got Delim: '$delim'";
		$self->{'client_state'} = "msg"; # waiting for a full message

		$self->{'message'} = "";

		# TODO: if we have any envelope options such as compression, parse them here.
		
		# TODO: Set alarms to timeout a read that takes too long
	    }
	    else
	    {
		WARN "Unknonw command: $line";
		$self->{'client_state'} = undef; # resets
	    }
	}
    }

    delete $handle->{'rbuf'};
    $self->_read_next_chunk() if (defined $self->{'client_handle'});
}

sub _read_next_chunk
{
    my $self = shift;

    DEBUG "read next chunk??";

    $self->{'client_handle'}->push_read( sub { $self->_read_command_chunk(@_); } );
}

sub add_message
{
    # add message to queue, and send msg-in-queue event
    my $self = shift;
    my ($message) = @_;

    DEBUG "Adding message.";

    push (@{$self->{'write_queue'}},$message);

    $self->event('new_outbound_message');
}

sub is_connected
{
    my $self = shift;
    
    return $self->{'connected'};
}

sub _send_next_message
{
    my $self = shift;

    DEBUG "connected: ".$self->{'connected'};

    return undef unless ($self->is_connected());

    DEBUG "SNM";

    # get the next message from our outbound queue
    my $message = $self->get_message('write_queue'); #actually removes it, so unshift it back on error

    # generate the message's one time delimeter
    my $one_time_delim = &MariposaDB::Utils::one_time_delim();

#    DEBUG "OTD: $one_time_delim";

    # do a push write of the command + msg + commandend
    # FIXME TODO: check to see if msg contains delim?
    
    $self->{'client_handle'}->push_write("msg $one_time_delim\n$message\nmsg $one_time_delim\n"); # and that's it.
    DEBUG "SNM, done";
    open (M, ">/tmp/m");
    print M $message;
    close M;

}

1;
